home *** CD-ROM | disk | FTP | other *** search
/ BCI NET 2 / BCI NET 2.iso / archives / programming / c / c2man-2.0pl33.lha / c2man-2.0 / manpage.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-01-24  |  34.5 KB  |  1,447 lines

  1. /* $Id: manpage.c,v 2.0.1.48 1994/09/16 05:54:36 greyham Exp $
  2.  * stuff to do with manual page outputing
  3.  */
  4.  
  5. #include "c2man.h"
  6.  
  7. #include <errno.h>
  8. #include <ctype.h>
  9.  
  10. #include "manpage.h"
  11. #include "strconcat.h"
  12. #include "strappend.h"
  13. #include "semantic.h"
  14. #include "output.h"
  15.  
  16. #ifdef I_SYS_FILE
  17. #include <sys/file.h>
  18. #endif
  19.  
  20. /* list of manual pages */
  21. ManualPage *firstpage = NULL;
  22. ManualPage **lastpagenext = &firstpage;
  23.  
  24. void dummy() {}
  25.  
  26. void
  27. new_manual_page(comment, decl_spec, declarator)
  28.      char *comment;
  29.      DeclSpec *decl_spec;
  30.      Declarator *declarator;
  31. {
  32.     ManualPage *newpage;
  33.  
  34.     /* check that we really want a man page for this */
  35.     if ((!comment) ||
  36.     !inbasefile ||
  37.     (!variables_out && !is_function_declarator(declarator)) ||
  38.     (decl_spec->flags & DS_JUNK) ||
  39.     (!static_out && (decl_spec->flags & DS_STATIC) && !header_file) ||
  40.  
  41.     /* only document extern stuff if it's in a header file, or includes a
  42.      * function definition.
  43.      */
  44.     ((decl_spec->flags & DS_EXTERN) && !header_file &&
  45.                     declarator->type != DECL_FUNCDEF))
  46.     {
  47.     free_decl_spec(decl_spec);
  48.     free_declarator(declarator);
  49.     safe_free(comment);
  50.     return;
  51.     }
  52.     
  53.     declarator->comment = comment;
  54.     
  55.     newpage = (ManualPage *)safe_malloc(sizeof *newpage);
  56.     newpage->decl_spec = (DeclSpec *)safe_malloc(sizeof *newpage->decl_spec);
  57.     newpage->declarator = declarator;
  58.  
  59.     *newpage->decl_spec = *decl_spec;
  60.     newpage->sourcefile = strduplicate(basefile);
  61.     newpage->sourcetime = basetime;
  62.  
  63.     *lastpagenext = newpage;
  64.     newpage->next = NULL;
  65.     lastpagenext = &newpage->next;
  66. }
  67.  
  68. void free_manual_page(page)
  69.      ManualPage *page;
  70. {
  71.     free_decl_spec(page->decl_spec);
  72.     free(page->decl_spec);
  73.     free_declarator(page->declarator);
  74.     safe_free(page->sourcefile);
  75. }
  76.  
  77. /* free the list of manual pages */
  78. void free_manual_pages(first)
  79.      ManualPage *first;
  80. {
  81.     ManualPage *page, *next;
  82.  
  83.     /* free any terse description read from the file */
  84.     if (group_terse && !terse_specified)
  85.     {
  86.         free(group_terse);
  87.     group_terse = NULL;
  88.     }
  89.     
  90.     for (page = first;page;page = next)
  91.     {
  92.     next = page->next;
  93.     free_manual_page(page);
  94.     free(page);
  95.     }
  96. }
  97.  
  98. /* allocate a substring starting at start, ending at end (NOT including *end) */
  99. char *alloc_string(start, end)
  100.      const char *start;
  101.      const char *end;
  102. {
  103.     int len = end - start;
  104.     char *ret;
  105.     if (len == 0)    return NULL;
  106.     
  107.     ret = (char *)safe_malloc((size_t)len+1);
  108.  
  109.     strncpy(ret,start,len);
  110.     ret[len] = '\0';
  111.  
  112.     return ret; 
  113. }
  114.  
  115. /* remember the terse description from the first comment in a file */
  116. void remember_terse(comment)
  117.      char *comment;
  118. {
  119.     char *c, *d;
  120.     
  121.     enum { STUFF, LEADSPACE, DASH, TRAILSPACE, FOUND } state = STUFF;
  122.  
  123.     /* if we've found a terse comment in a previous file, or one was
  124.      * specified on the command line, forget it.
  125.      */
  126.     if (group_terse)    return;
  127.  
  128.     /* look for a whitespace surrounded sequence of dashes to skip */
  129.     for (c = comment;*c && state != FOUND;c++)
  130.     {
  131.     switch (state)
  132.     {
  133.     case STUFF:    if (isspace(*c))    state = LEADSPACE;
  134.             break;
  135.     case LEADSPACE:    if (*c == '-')        state = DASH;
  136.             else if (!isspace(*c))    state = STUFF;
  137.             break;
  138.     case DASH:    if (isspace(*c))    state = TRAILSPACE;
  139.             else if (*c != '-')    state = STUFF;
  140.             break;
  141.     case TRAILSPACE:if (!isspace(*c))    { c--; state = FOUND; }
  142.             break;
  143.     case FOUND:    break;
  144.     }
  145.     }
  146.  
  147.     /* if no dashes were found, go back to the start */
  148.     if (state != FOUND)    c = comment;
  149.  
  150.     d = c + 1;
  151.     
  152.     while (*d && *d != '\n')
  153.     d++;
  154.  
  155.     group_terse = alloc_string(c,d);
  156. }
  157.  
  158. /* output a comment in man page form, followed by a newline */
  159. void
  160. output_comment(comment)
  161. const char *comment;
  162. {
  163.     enum { TEXT, PERIOD, CAPITALISE } state = CAPITALISE;
  164.     boolean new_line = TRUE;
  165.     boolean dot_command = FALSE;
  166.     
  167.     if (!comment || !comment[0])
  168.     {
  169.     output->text("Not Documented.\n");
  170.     return;
  171.     }
  172.  
  173.     /* correct punctuation a bit as it goes out */
  174.     for (;*comment;comment++)
  175.     {
  176.     int c = *comment;
  177.  
  178.     if (dot_command)
  179.     {
  180.         if (c == '\n')    dot_command = FALSE;
  181.     }
  182.     else if (new_line && c == '.')
  183.         dot_command = TRUE;
  184.     else if (new_line && (c == '-' || c == '*'))
  185.     {
  186.         output->break_line();
  187.         state = CAPITALISE;
  188.     }
  189.     else if (c == '.')
  190.         state = PERIOD;
  191.     else if (isspace(c) && state == PERIOD)
  192.         state = CAPITALISE;
  193.     else if (isalnum(c) && state == CAPITALISE)
  194.     {   
  195.         if (islower(c))    c = toupper(c);
  196.         state = TEXT;
  197.     }
  198.            
  199.     output->character(c);
  200.     new_line = c == '\n';
  201.     }
  202.  
  203.     /* do a full stop if there wasn't one */
  204.     if (!dot_command && state == TEXT)    output->character('.');
  205.  
  206.     output->character('\n');
  207. }
  208.  
  209. /* output the returns section in man page form, followed by a newline */
  210. void
  211. output_returns(comment)
  212. char *comment;
  213. {
  214.     enum { TEXT, PERIOD, CAPITALISE } state = CAPITALISE;
  215.     char lastchar = '\n';
  216.     boolean tag_list_started = FALSE;
  217.  
  218.     /* for each line... */
  219.     while (*comment)
  220.     {
  221.     boolean tagged = FALSE;
  222.  
  223.     /* explicitly reject dot commands */
  224.     if (*comment && *comment != '.')
  225.     {
  226.         char *c = comment;
  227.  
  228.         /* search along until the end of a word */
  229.         while (*c && *c != ':' && !isspace(*c))
  230.         c++;
  231.  
  232.         /* skip all spaces or tabs after the first word */
  233.         while (*c && *c != '\n')
  234.         {
  235.         if (*c == '\t' || *c == ':')
  236.         {
  237.             tagged = TRUE;
  238.             break;
  239.         }
  240.         else if (!isspace(*c))
  241.             break;
  242.  
  243.         c++;
  244.         }
  245.     }
  246.  
  247.     /* is it tagged?; explicitly reject dot commands */
  248.     if (tagged)
  249.     {
  250.         /* output lingering newline if necessary */
  251.         if (lastchar != '\n')
  252.         {
  253.         if (state == TEXT && !ispunct(lastchar))    output->character('.');
  254.         output->character(lastchar = '\n');
  255.         }
  256.  
  257.         if (!tag_list_started)
  258.         {
  259.         output->tag_list_start();
  260.         tag_list_started = TRUE;
  261.         }
  262.  
  263.         /* output the taggy bit */
  264.         output->tag_entry_start();
  265.         while (*comment && *comment != ':' && !isspace(*comment))
  266.         output->character(*comment++);
  267.         output->tag_entry_end();
  268.  
  269.         /* skip any extra tabs or spaces */
  270.         while (*comment == ':' || (isspace(*comment) && *comment != '\n'))
  271.         comment++;
  272.  
  273.         state = CAPITALISE;
  274.     }
  275.  
  276.     /* terminate the previous line if necessary */
  277.     if (lastchar != '\n')    output->character(lastchar = '\n');
  278.  
  279.     /* dot commands go out unaltered */
  280.     if (*comment == '.')
  281.     {
  282.         for (;*comment && *comment != '\n'; comment++)
  283.         output->character(*comment);
  284.         output->character('\n');
  285.     }
  286.     else
  287.     {
  288.         /* correct punctuation a bit as the line goes out */
  289.         for (;*comment && *comment != '\n'; comment++)
  290.         {
  291.         char c = *comment;
  292.  
  293.         if (c == '.')
  294.             state = PERIOD;
  295.         else if (isspace(c) && state == PERIOD)
  296.             state = CAPITALISE;
  297.         else if (isalnum(c) && state == CAPITALISE)
  298.         {   
  299.             if (islower(c))    c = toupper(c);
  300.             state = TEXT;
  301.         }
  302.  
  303.         output->character(lastchar = c);
  304.         }
  305.  
  306.         /* if it ended in punctuation, just output the nl straight away. */
  307.         if (ispunct(lastchar))
  308.         {
  309.         if (lastchar == '.')    state = CAPITALISE;
  310.         output->character(lastchar = '\n');
  311.         }
  312.     }
  313.  
  314.     if (*comment)    comment++;
  315.     }
  316.  
  317.     /* output lingering newline if necessary */
  318.     if (lastchar != '\n')
  319.     {
  320.     if (state == TEXT && !ispunct(lastchar))    output->character('.');
  321.     output->character('\n');
  322.     }
  323.  
  324.     if (tag_list_started)
  325.     output->tag_list_end();
  326.  
  327. }
  328.  
  329. /* output the phrase "a[n] <type name>" */
  330. void output_conjunction(text)
  331. char *text;
  332. {
  333.     output->character('a');
  334.     if (strchr("aAeEiIoOuU",text[0]))    output->character('n');
  335.     output->character(' ');
  336.     output->code(text);
  337. }
  338.  
  339. /* output the description for an identifier; be it return value or param */
  340. static void output_identifier_description(comment, outfunc,
  341.                             decl_spec, declarator)
  342.     const char *comment;        /* comment for this identifier */
  343.     void (*outfunc) _((const char *));    /* function to output comment */
  344.     const DeclSpec *decl_spec;
  345.     const Declarator *declarator;
  346. {
  347.     /* one day, this may document the contents of structures too */
  348.  
  349.     /* output list of possible enum values, if any */
  350.     if (decl_spec->enum_list)
  351.     {
  352.     int maxtaglen = 0;
  353.     char *longestag = NULL;
  354.     int descriptions = 0;
  355.     int entries = 0;
  356.     Enumerator *e;
  357.     int is_first = 1;
  358.     boolean started = FALSE;
  359.     
  360.     /* don't output the "Not Doc." message for enums */
  361.     if (comment)
  362.     {
  363.         (*outfunc)(comment);
  364.         output->blank_line();
  365.     }
  366.     
  367.     /* see if any have descriptions */
  368.     for (e = decl_spec->enum_list->first; e; e = e->next)
  369.         if (e->name[0] != '_')
  370.         {
  371.         int taglen = strlen(e->name);
  372.         if (taglen > maxtaglen)
  373.         {
  374.             maxtaglen = taglen;
  375.             longestag = e->name;
  376.         }
  377.         if (e->comment)    descriptions = 1;
  378.         entries++;
  379.         }
  380.  
  381.     /* if there are a lot of them, the list may be automatically generated,
  382.      * and probably isn't wanted in every manual page.
  383.      */
  384.     if (entries > 20)
  385.     {
  386.         char entries_s[15];
  387.         sprintf(entries_s, "%d", entries);
  388.         output->text("Since there are ");
  389.         output->text(entries_s);
  390.         output->text(" possible values for ");
  391.         output_conjunction(decl_spec->text);
  392.         output->text(", they are not all listed here.\n");
  393.     }
  394.     else if (entries > 0)   /* skip the pathological case */
  395.     {
  396.         /* the number of possibilities is reasonable; list them all */
  397.         output->text("Possible values for ");
  398.         output_conjunction(decl_spec->text);
  399.         output->text(" are as follows:\n");
  400.     
  401.         for (e = decl_spec->enum_list->first; e; e = e->next)
  402.         {
  403.         /* don't print names with a leading underscore! */
  404.         if (e->name[0] == '_')    continue;
  405.         
  406.         if (e->group_comment)
  407.         {
  408.             /* break out of table mode for the group comment */
  409.             if (started)
  410.             {
  411.             if (descriptions)
  412.                 output->table_end();
  413.             else
  414.                 output->list_end();
  415.             started = FALSE;
  416.             }
  417.             output->indent();
  418.             output_comment(e->group_comment);
  419.         }
  420.     
  421.         if (!started)
  422.         {
  423.             if (descriptions)
  424.             output->table_start(longestag);
  425.             else
  426.             output->list_start();
  427.             started = TRUE;
  428.         }
  429.     
  430.         if (descriptions)
  431.             output->table_entry(e->name, e->comment);
  432.         else
  433.         {
  434.             if (!is_first)
  435.             output->list_separator();
  436.             is_first = 0;
  437.             output->list_entry(e->name);
  438.         }
  439.         }
  440.     
  441.         if (started)
  442.         {
  443.         if (descriptions)
  444.             output->table_end();
  445.         else
  446.             output->list_end();
  447.         }
  448.     }
  449.     } 
  450.     else
  451.     (*outfunc)(comment);
  452. }
  453.  
  454. /* is there automatic documentation here? */
  455. static boolean auto_documented(page)
  456. const ManualPage *page;
  457. {
  458.     /* one day we may handle structs too */
  459.     return
  460.     page->decl_spec->enum_list != NULL;    /* enums are self-documenting. */
  461. }
  462.  
  463. /* decide if a manual page needs a RETURNS section.
  464.  * If this is true, then output_identifier_description must be able to generate
  465.  * sensible output for it.
  466.  */
  467. static boolean needs_returns_section(page)
  468. const ManualPage *page;
  469. {
  470.     return 
  471.     (page->returns && page->returns[0]) ||
  472.     (auto_documented(page) && is_function_declarator(page->declarator));
  473. }
  474.  
  475. /* does this declarator have documented parameters? */
  476. boolean has_documented_parameters(d)
  477. const Declarator *d;
  478. {
  479.     if (has_parameters(d))
  480.     {
  481.     Parameter *p;
  482.  
  483.     for (p = d->head->params.first; p != NULL; p = p->next)
  484.         if (p->declarator->comment || always_document_params)
  485.         return TRUE;
  486.     }
  487.     return FALSE;
  488. }
  489.  
  490. /* Output the list of function parameter descriptions.
  491.  */
  492. void
  493. output_parameter_descriptions (params, function)
  494. ParameterList *params;
  495. char *function;
  496. {
  497.     Parameter *p;
  498.     boolean tag_list_started = FALSE;
  499.  
  500.     for (p = params->first; p != NULL; p = p->next)
  501.     {
  502.     if (p->suppress ||
  503.         (!always_document_params && p->declarator->comment == NULL))
  504.         continue;
  505.  
  506.     if (!tag_list_started)
  507.     {
  508.       output->tag_list_start();
  509.       tag_list_started = TRUE;
  510.     }
  511.  
  512.     if (p->duplicate)
  513.         output->tag_entry_start_extra();
  514.     else
  515.         output->tag_entry_start();
  516.  
  517.     output_parameter(p);
  518.  
  519.     /* include function name if it's a duplicate */
  520.     if (p->duplicate)
  521.         output->tag_entry_end_extra(function);
  522.     else
  523.         output->tag_entry_end();
  524.  
  525.     output_identifier_description(p->declarator->comment, output_comment,
  526.                         &p->decl_spec, &p->declarator);
  527.     }
  528.  
  529.     if (tag_list_started)
  530.     output->tag_list_end();
  531. }
  532.  
  533. /* split out the 'Returns:' section of a function comment */
  534. boolean
  535. split_returns_comment(comment, description, returns)
  536.      char *comment;
  537.      char **description;
  538.      char **returns;
  539. {
  540.     char *retstart;
  541.  
  542.     for (retstart = comment;
  543.      retstart;
  544.      retstart = strchr(retstart,'\n'))
  545.     {
  546.     if (*retstart == '\n')    retstart++;    /* skip the newline */
  547.  
  548.     if (!strncmpi(retstart, "returns",(size_t)7))
  549.     {        
  550.         char *descend = retstart - 2;    /* back before newline */
  551.  
  552.         /* go back to the end of the description in case there were
  553.          * linefeeds before the returns.
  554.          */
  555.         while (descend > comment && isspace(*descend))
  556.         descend--;
  557.  
  558.         *description =
  559.         descend > comment ? alloc_string(comment,descend+1) : NULL;
  560.  
  561.         retstart += 7;
  562.         
  563.         while (*retstart == ':' || isspace(*retstart))
  564.         retstart++;
  565.  
  566.         if (*retstart)
  567.         *returns = strduplicate(retstart);
  568.         else
  569.             *returns = NULL;
  570.         return TRUE;
  571.     }
  572.     }
  573.  
  574.     *description = comment;
  575.     *returns = NULL;
  576.     return FALSE;
  577. }
  578.  
  579. /* skip to past the dash on the first line, if there is one
  580.  * The dash must be surrounded by whitespace, so hyphens are not skipped.
  581.  */
  582. const char *skipdash(c)
  583. const char *c;
  584. {
  585.     const char *d;
  586.  
  587.     /* ignore anything on the first line, up to a dash (if any) */
  588.     for (d = c + 1; *d && *d != '\n' && *d != '-'; d++)
  589.     ;
  590.  
  591.     if (isspace(d[-1]) && d[0] == '-' && isspace(d[1]))
  592.     {
  593.     do
  594.         d++;
  595.     while (*d && *d != '\n' && isspace(*d));
  596.  
  597.     if (*d && *d != '\n')    c = d;
  598.     }
  599.     return c;
  600. }
  601.  
  602. /* split the function comment into manual page format */
  603. void
  604. split_function_comment(comment, identifier_name,
  605.                     terse, description, returns, extra_sections)
  606.     const char *comment;
  607.     const char *identifier_name;
  608.      char **terse;
  609.      char **description;
  610.      char **returns;
  611.      Section **extra_sections;
  612. {
  613.     const char *c, *start_text = NULL, *end_text = NULL;
  614.     char **put_ptr = NULL;
  615.     Section *first_section, **lastnextsection = &first_section;
  616.     boolean explicit_description = FALSE;
  617.     boolean lastblank = TRUE;
  618.     boolean skip_dash = FALSE;
  619.     
  620.     *description = *returns = NULL;
  621.     if (terse)    *terse = NULL;
  622.  
  623.     /* for each line... */
  624.     for (c = comment; *c;)
  625.     {
  626.     const char *start_line = c;
  627.     boolean section_heading;
  628.     /* remember if it's a blank line */
  629.     if (*c == '\n')
  630.     {
  631.         lastblank = TRUE;
  632.         c++;
  633.         continue;
  634.     }
  635.  
  636.     /* if the last one was blank, perhaps this one is a section heading
  637.      */
  638.     if (lastblank)
  639.     {
  640.         /* see if we've found the start of a SECTION */
  641.         while (isalpha(*c))
  642.         c++;
  643.     
  644.         section_heading = *c == '\n' || *c == ':' ||
  645.                     (*c == '\0' && start_line == comment);
  646.     }
  647.     else
  648.         section_heading = FALSE;
  649.  
  650.     lastblank = FALSE;    /* this one's not blank; for next time */
  651.  
  652.     if (section_heading)
  653.     {
  654.         size_t section_len = c - start_line; /* length of section name */
  655.  
  656.         /* yes, we've found a SECTION; store the previous one (if any) */
  657.         if (put_ptr && start_text)
  658.         {
  659.         if (skip_dash)    start_text = skipdash(start_text);
  660.         *put_ptr = alloc_string(start_text,end_text);
  661.         }
  662.  
  663.         skip_dash = FALSE;
  664.  
  665.         /* check for comments that start with the name of the identifier */
  666.         if (start_line == comment &&
  667.         !strncmp(start_line, identifier_name, section_len))
  668.         {
  669.         put_ptr = description;
  670.         }
  671.  
  672.         /* only accept NAME if not grouped */
  673.         else if (terse && 
  674.              (!strncmpi(start_line,"NAME", section_len) ||
  675.               !strncmpi(start_line,"FUNCTION", section_len) ||
  676.               !strncmpi(start_line,"PROCEDURE", section_len) ||
  677.               !strncmpi(start_line,"ROUTINE", section_len))
  678.              )
  679.  
  680.         {
  681.         put_ptr = terse;
  682.         skip_dash = TRUE;
  683.         }
  684.         else if (!strncmpi(start_line,"DESCRIPTION", section_len))
  685.         {
  686.         explicit_description = TRUE;
  687.         put_ptr = description;
  688.         }
  689.         else if (!strncmpi(start_line,"RETURNS", section_len))
  690.         {
  691.         put_ptr = returns;
  692.         }
  693.         else
  694.         {
  695.         /* allocate a new section */
  696.         Section *new_section =
  697.                 (Section *)safe_malloc(sizeof *new_section);
  698.  
  699.         *lastnextsection = new_section;
  700.         lastnextsection = &new_section->next;
  701.  
  702.         new_section->name = alloc_string(start_line,c);
  703.         strtoupper(new_section->name);
  704.         new_section->text = NULL;
  705.         new_section->been_output = FALSE; /* not been output yet */
  706.         put_ptr = &new_section->text;
  707.         }
  708.  
  709.         /* defer decision about where text starts till we find some */
  710.         start_text = NULL;
  711.  
  712.         if (*c == ':')    /* skip the terminating : */
  713.         {
  714.         c++;
  715.  
  716.         /* skip forward to the start of the text */
  717.         while (*c && *c != '\n' && isspace(*c))
  718.             c++;
  719.  
  720.         /* if we find the text here, then we've got it */
  721.         if (*c && *c != '\n')
  722.             start_text = c;
  723.         }
  724.     }
  725.     else
  726.     {
  727.         /* are we looking at the top of the function comment? */
  728.         if (start_line == comment)
  729.         {
  730.         /* only look for terse comment if not grouped together */
  731.         if (terse)
  732.         {
  733.             const char *endterse, *afterdash = skipdash(start_line);
  734.  
  735.             /* find the end of the terse comment */
  736.             while (*c && *c != '.' && *c != '\n')
  737.             c++;
  738.  
  739.             endterse = *c == '.' ? c+1 : c;
  740.             *terse = alloc_string(
  741.             afterdash < endterse ? afterdash : start_line,
  742.             endterse);
  743.  
  744.             /* skip it if it's a ., and any trailing spaces */
  745.             if (*c == '.')
  746.             do c++; while (*c && *c != '\n' && isspace(*c));
  747.  
  748.             start_text = NULL;    /* look for it */
  749.  
  750.             if (*c && *c != '\n')
  751.             /* actually, it's a description, starting here */
  752.             start_text = c;
  753.         }
  754.         /* must be a description starting at the beginning of the line.
  755.          */
  756.         else
  757.             start_text = start_line;
  758.  
  759.         put_ptr = description;
  760.         }
  761.         else
  762.         /* have we just located the first real text in a section? */
  763.         if (put_ptr && !start_text)    start_text = start_line;
  764.     }
  765.  
  766.     /* skip the line */
  767.     if (*c && *c != '\n')
  768.         while (*c && *c != '\n')    c++;
  769.  
  770.     end_text = c;    /* so far, the text ends at the end of this line */
  771.     if (*c)    c++;
  772.     }
  773.  
  774.     /* store the last one */
  775.     if (put_ptr && start_text)
  776.     {
  777.     if (skip_dash)    start_text = skipdash(start_text);
  778.     *put_ptr = alloc_string(start_text,end_text);
  779.     }
  780.  
  781.     /* terminate (or nuke) section list */
  782.     *lastnextsection = NULL;
  783.  
  784.     /* if there wasn't a RETURNS section, and the DESCRIPTION field was not
  785.      * explicit, see if we can split one out of the description field.
  786.      */
  787.     if (*returns == NULL && !explicit_description)
  788.     {
  789.     char *olddesc = *description;
  790.         if (split_returns_comment(olddesc, description, returns))
  791.         free(olddesc);
  792.     }
  793.  
  794.     *extra_sections = first_section;
  795. }
  796.  
  797. /* see if two parameters are declared identically */
  798. boolean params_identical(first, second)
  799.      Parameter *first;
  800.      Parameter *second;
  801. {
  802.     return
  803.     first->decl_spec.flags == second->decl_spec.flags &&
  804.  
  805.     /* there may be no decl_spec.text if it's an ellipsis arg */
  806.     ((!first->decl_spec.text && !second->decl_spec.text) ||
  807.      (first->decl_spec.text && second->decl_spec.text &&
  808.       !strcmp(first->decl_spec.text, second->decl_spec.text))) &&
  809.  
  810.     ((!first->declarator->text && !second->declarator->text) ||
  811.      (first->declarator->text && second->declarator->text &&
  812.       !strcmp(first->declarator->text, second->declarator->text)));
  813. }
  814.  
  815. /* search all the parameters in this grouped manual page for redundancies */
  816. boolean mark_duplicate_parameters(firstpage)
  817.      ManualPage *firstpage;
  818. {
  819.     Parameter *param;
  820.     boolean any = FALSE;
  821.     ManualPage *page;
  822.  
  823.     for (page = firstpage; page; page = page->next)
  824.     {
  825.     if (has_parameters(page->declarator))
  826.     for (param = page->declarator->head->params.first; param;
  827.                             param = param->next)
  828.     {
  829.         ManualPage *otherpage;
  830.         Parameter *otherparam;
  831.         
  832.         if (always_document_params || param->declarator->comment)
  833.         any = TRUE;
  834.  
  835.         for (otherpage = page->next; otherpage;
  836.                         otherpage = otherpage->next)
  837.         {
  838.         if (has_parameters(otherpage->declarator))
  839.         for (otherparam = otherpage->declarator->head->params.first;
  840.              otherparam;
  841.              otherparam = otherparam->next)
  842.         {
  843.             /* do these two look the same? */
  844.             if (params_identical(param, otherparam))
  845.             {
  846.             /* order is important for bit positions */
  847.             enum { NEITHER, US, THEM, BOTH } has_comm = NEITHER;
  848.             
  849.             /* work out who has the comment */
  850.             if (param->declarator->comment)    has_comm |= US;
  851.             if (otherparam->declarator->comment) has_comm |= THEM;
  852.  
  853.             switch(has_comm)
  854.             {
  855.             case NEITHER:
  856.             case US:
  857.                 otherparam->suppress = TRUE;
  858.                 break;
  859.             case THEM:
  860.                 param->suppress = TRUE;
  861.                 break;
  862.             case BOTH:
  863.                 if (!strcmp(param->declarator->comment,
  864.                         otherparam->declarator->comment))
  865.                 otherparam->suppress = TRUE;
  866.                 else
  867.                 {
  868.                 param->duplicate = TRUE;
  869.                 otherparam->duplicate = TRUE;
  870.                 }
  871.                 break;
  872.             }
  873.             }
  874.         }
  875.         }
  876.     }
  877.     }
  878.     return any;
  879. }
  880.  
  881. /* output a formatting string so that it works with filling on */
  882. void output_format_string(fmt)
  883. const char *fmt;
  884. {
  885.     while (*fmt)
  886.     {
  887.     output->character(*fmt);
  888.  
  889.     if (*fmt++ == '\n')
  890.         output->break_line();    /* break the line */
  891.     }
  892. }
  893.  
  894. /* write the warning for the header */
  895. void output_warning()
  896. {
  897.     output->comment();
  898.     output->text("WARNING! THIS FILE WAS GENERATED AUTOMATICALLY BY ");
  899.     output->text(progname);
  900.     output->text("!\n");
  901.     output->comment();
  902.     output->text("DO NOT EDIT! CHANGES MADE TO THIS FILE WILL BE LOST!\n");
  903. }
  904.  
  905. void output_includes()
  906. {
  907.     IncludeFile *incfile;
  908.     
  909.     for (incfile = first_include; incfile; incfile=incfile->next)
  910.     {
  911.     char *name = incfile->name;
  912.     boolean surrounded = *name == '"' || *name == '<';
  913.     
  914.     output->text("#include ");
  915.     if (!surrounded)    output->character('<');
  916.     output->text(name);
  917.     if (!surrounded)    output->character('>');
  918.     output->text("\n");
  919.     output->break_line();
  920.     }
  921. }
  922.  
  923. int exclude_section(section)
  924. const char *section;
  925. {
  926.     ExcludeSection *exclude;
  927.  
  928.     for (exclude = first_excluded_section ; exclude ; exclude = exclude->next)
  929.     if (!strcmp(section, exclude->name)) return 1;
  930.  
  931.     return 0;
  932. }
  933.  
  934.  
  935. /* Writes the entire contents of the manual page specified by basepage. */
  936. void
  937. output_manpage(firstpage, basepage, input_files, title, section)
  938.     /* the first page in the list of all manual pages.  This is used to build
  939.      * the SEE ALSO section of related pages when group_together is false.
  940.      */
  941.     ManualPage *firstpage;
  942.  
  943.     /* the base page from which the output manual page will be generated.  if
  944.      * group_together indicates that the user wanted grouped pages, basepage
  945.      * will always be the same as firstpage, and all the ManualPage's in the
  946.      * list will be grouped together into the one output page.
  947.      */
  948.     ManualPage *basepage;
  949.  
  950.     int input_files;
  951.     const char *title;
  952.     const char *section;
  953. {
  954.     ManualPage *page;
  955.     boolean need_returns;
  956.     char *terseout, *terse = NULL;
  957.     
  958.     /* check if there's more than one page in the group */
  959.     boolean grouped = group_together && firstpage->next;
  960.  
  961.     /* split up all the function comments for this page */
  962.     for (page = basepage; page; page = page->next)
  963.     {
  964.     split_function_comment(page->declarator->comment,
  965.         page->declarator->name,
  966.         group_together ? (char **)NULL : &terse,
  967.         &page->description,&page->returns,&page->first_section);
  968.     if (!group_together)    break;
  969.     }
  970.  
  971.     /* work out what we'll actually print as a terse description */
  972.     terseout = group_terse ? group_terse : (terse ? terse : "Not Described");
  973.  
  974.     output->header(basepage, input_files, grouped,
  975.             title ? title : basepage->declarator->name, section);
  976.     
  977.     output->name(NULL);
  978.     /* output the names of all the stuff documented on this page */
  979.     for (page = basepage; page; page = page->next)
  980.     {
  981.     output->name(page->declarator->name);
  982.  
  983.     if (!group_together)    break;
  984.  
  985.     if (page->next)    output->text(",\n");
  986.     }
  987.  
  988.     output->terse_sep();
  989.     output->text(terseout);
  990.     output->character('\n');
  991.     
  992.     output->section("SYNOPSIS");
  993.  
  994.     output->code_start();
  995.     
  996.     /* list the include files the user asked us to */
  997.     output_includes();
  998.  
  999.     /* if it's a header file, say to #include it */
  1000.     if (header_file)
  1001.     {
  1002.     output->text("#include <");
  1003.     if (header_prefix)
  1004.     {
  1005.         output->text(header_prefix);
  1006.         output->character('/');
  1007.     }
  1008.     output->text(basefile);
  1009.     output->text(">\n");
  1010.     }
  1011.  
  1012.     /* can't just use .PP; that may reset our font */
  1013.     if (first_include || header_file)    output->blank_line();
  1014.  
  1015.     for (page = basepage; page; page = page->next)
  1016.     {
  1017.     output_format_string(decl_spec_prefix);
  1018.  
  1019.     /* make sure variables are prefixed extern */
  1020.     if (!(page->decl_spec->flags & DS_STATIC) &&
  1021.         !is_function_declarator(page->declarator) &&
  1022.         !strstr(page->decl_spec->text, "extern"))
  1023.         output->text("extern ");
  1024.  
  1025.     output_decl_spec(page->decl_spec);
  1026.     output_format_string(declarator_prefix);
  1027.  
  1028.     /* format it nicely if there's more than one parameter */
  1029.     output_declarator(page->declarator, 
  1030.         page->declarator->head->params.first !=
  1031.         page->declarator->head->params.last);
  1032.  
  1033.     output->text(";\n");
  1034.     
  1035.     if (!grouped)    break;
  1036.     if (page->next)    output->blank_line();
  1037.     }
  1038.  
  1039.     output->code_end();
  1040.  
  1041.     /* only output paramaters if there actually are some,
  1042.      * not including merely (void)
  1043.      */
  1044.     if ((grouped && mark_duplicate_parameters(basepage)) ||
  1045.         (!grouped && has_documented_parameters(basepage->declarator)))
  1046.     {
  1047.     output->section("PARAMETERS");
  1048.  
  1049.     for (page = basepage; page; page = page->next)
  1050.     {
  1051.         if (has_parameters(page->declarator))
  1052.         output_parameter_descriptions(&page->declarator->head->params,
  1053.                             page->declarator->name);
  1054.         if (!grouped)    break;    /* only do first page */
  1055.     }
  1056.     }
  1057.  
  1058.     output->section("DESCRIPTION");
  1059.  
  1060.     if (grouped)
  1061.     {
  1062.     need_returns = FALSE;
  1063.     for (page = basepage; page; page = page->next)
  1064.     {
  1065.         if (needs_returns_section(page))    need_returns = TRUE;
  1066.  
  1067.         /* enum variables are documented in DESCRIPTION */
  1068.         if (auto_documented(page) &&
  1069.                     !is_function_declarator(page->declarator))
  1070.         {
  1071.         output->sub_section(page->declarator->name);
  1072.         output_identifier_description(page->description,
  1073.             output_comment, page->decl_spec, page->declarator);
  1074.         }
  1075.         else if (page->description)
  1076.         {
  1077.         output->sub_section(page->declarator->name);
  1078.         output_comment(page->description);
  1079.         }
  1080.  
  1081.         safe_free(page->description);
  1082.     }
  1083.     }
  1084.     else
  1085.     {
  1086.     const char *descr = basepage->description ? basepage->description
  1087.                            : terseout;
  1088.  
  1089.     need_returns = needs_returns_section(basepage);
  1090.  
  1091.     if (auto_documented(page) && !is_function_declarator(page->declarator))
  1092.         output_identifier_description(descr, output_comment,
  1093.                         page->decl_spec, page->declarator);
  1094.     else
  1095.         output_comment(descr);
  1096.  
  1097.     safe_free(basepage->description);
  1098.  
  1099.     }
  1100.  
  1101.     /* terse can now never be a static string */
  1102.     safe_free(terse);
  1103.  
  1104.     if (need_returns)
  1105.     {
  1106.     output->section("RETURNS");
  1107.  
  1108.     for (page = basepage; page; page = page->next)
  1109.     {
  1110.         if (needs_returns_section(page))
  1111.         {
  1112.         if (grouped) output->sub_section(page->declarator->name);
  1113.  
  1114.         output_identifier_description(page->returns, output_returns,
  1115.                         page->decl_spec, page->declarator);
  1116.         safe_free(page->returns);
  1117.         }
  1118.  
  1119.         if (!grouped)    break;
  1120.     }
  1121.     }
  1122.  
  1123.     /* output any other sections */
  1124.     for (page = basepage; page; page = page->next)
  1125.     {
  1126.     Section *section, *next;
  1127.  
  1128.     for (section = page->first_section; section; section = next)
  1129.     {
  1130.         next = section->next;
  1131.  
  1132.         if (!section->been_output && section->text &&
  1133.         strncmpi(section->text,"none",4) &&
  1134.          !exclude_section(section->name))
  1135.         {
  1136.         output->section(section->name);
  1137.         if (grouped) output->sub_section(page->declarator->name);
  1138.         output_comment(section->text);
  1139.         section->been_output = TRUE;
  1140.     
  1141.         if (grouped && page->next)
  1142.         {
  1143.             ManualPage *other_page = page->next;
  1144.     
  1145.             /* look through all the other pages for matching sections */
  1146.             for (; other_page; other_page = other_page->next)
  1147.             {
  1148.             Section *other_section = other_page->first_section;
  1149.             for (;other_section; other_section =
  1150.                                 other_section->next)
  1151.             {
  1152.                 if (other_section->been_output ||
  1153.                 strcmp(other_section->name, section->name))
  1154.                 continue;
  1155.     
  1156.                 output->sub_section(other_page->declarator->name);
  1157.                 output_comment(other_section->text);
  1158.                 other_section->been_output = TRUE;
  1159.             }
  1160.             }
  1161.         }
  1162.         }
  1163.  
  1164.  
  1165.         /* free this section */
  1166.         free(section->name);
  1167.         safe_free(section->text);
  1168.         free(section);
  1169.     }
  1170.  
  1171.     if (!grouped)    break;
  1172.     }
  1173.  
  1174.     /* only output SEE ALSO if not grouped */
  1175.     if (!group_together)
  1176.     {
  1177.     ManualPage *also;
  1178.  
  1179.     /* add the SEE ALSO section */
  1180.     /* look for any other functions to refer to */
  1181.     for (also = firstpage; also && also == basepage; also = also->next)
  1182.         ;
  1183.     
  1184.     if (also && !exclude_section("SEE ALSO"))    /* did we find at least one? */
  1185.     {
  1186.         int isfirst = 1;
  1187.  
  1188.         output->section("SEE ALSO");
  1189.         
  1190.         for (also = firstpage; also; also = also->next)
  1191.         {
  1192.         if (also == basepage)    continue;
  1193.         
  1194.         if (!isfirst)
  1195.             output->text(",\n");
  1196.         else
  1197.             isfirst = 0;
  1198.             
  1199.         output->reference(also->declarator->name);
  1200.         }
  1201.     
  1202.         output->character('\n');
  1203.     }
  1204.     }
  1205.  
  1206.     if (!make_embeddable)
  1207.     output->file_end();
  1208. }
  1209.  
  1210.  
  1211. /* generate output filename based on a string */
  1212. char *page_file_name(based_on, object_type, extension)
  1213.     /* string to base the name on; this will be the name of an identifier or
  1214.      * the base of the input file name.
  1215.      */
  1216.     const char *based_on;
  1217.     enum Output_Object object_type;    /* class of object documented */
  1218.     const char *extension;        /* file extension to use */
  1219. {
  1220.     char *filename;
  1221.     const char *subdir = output_object[object_type].subdir;
  1222.  
  1223. #ifndef FLEXFILENAMES
  1224.     char *basename;
  1225.     int chopoff = 14 - strlen(extension) - 1;
  1226.  
  1227.     basename = strduplicate(based_on);
  1228.     if (strlen(basename) > chopoff)
  1229.     basename[chopoff] = '\0';
  1230. #else
  1231.     const char *basename = based_on;
  1232. #endif
  1233.  
  1234.     filename = strduplicate(output_dir);
  1235.  
  1236.     if (subdir)
  1237.     {
  1238.     if (filename)    filename = strappend(filename, "/", NULLCP);
  1239.     filename = strappend(filename, subdir, NULLCP);
  1240.     }
  1241.  
  1242.     if (filename)    filename = strappend(filename, "/", NULLCP);
  1243.     filename = strappend(filename, basename,".",extension, NULLCP);
  1244.  
  1245. #ifndef FLEXFILENAMES
  1246.     free(basename);
  1247. #endif
  1248.     return filename;
  1249. }
  1250.  
  1251. /* determine the output page type from a declaration */
  1252. enum Output_Object page_output_type(decl_spec, declarator)
  1253. const DeclSpec *decl_spec;
  1254. const Declarator *declarator;
  1255. {
  1256.     boolean is_static = decl_spec->flags & DS_STATIC;
  1257.     return is_function_declarator(declarator)
  1258.     ? (is_static ? OBJECT_STATIC_FUNCTION : OBJECT_FUNCTION)
  1259.     : (is_static ? OBJECT_STATIC_VARIABLE : OBJECT_VARIABLE);
  1260. }
  1261.  
  1262. /* determine the extension/section from an output type */
  1263. const char *page_manual_section(output_type)
  1264. enum Output_Object output_type;
  1265. {
  1266.     return    output_object[output_type].extension ?
  1267.         output_object[output_type].extension : manual_section;
  1268. }
  1269.  
  1270. /* remove an existing file, if it exists & we have write permission to it */
  1271. int remove_old_file(name)
  1272. const char *name;
  1273. {
  1274. #ifdef HAS_ACCESS
  1275.     /* check that we have write premission before blasting it */
  1276.     if (access(name,W_OK) == -1)
  1277.     {
  1278.     if (errno != ENOENT)
  1279.     {
  1280.         my_perror("can't access output file", name);
  1281.         return FALSE;
  1282.     }
  1283.      }
  1284.     else
  1285. #endif
  1286.     {
  1287.     /* if it exists, blast it */
  1288.     if (unlink(name) == -1 && errno != ENOENT)
  1289.     {
  1290.         my_perror("error unlinking old link file", name);
  1291.         return FALSE;
  1292.     }
  1293.     }
  1294.     return TRUE;
  1295. }
  1296.  
  1297. /* output all the manual pages in a list */
  1298. void output_manual_pages(first, input_files, link_type)
  1299.     ManualPage *first;
  1300.     int input_files;    /* number of different input files */
  1301.     enum LinkType link_type;    /* how grouped pages will be linked */
  1302. {
  1303.     ManualPage *page;
  1304.     int tostdout = output_dir && !strcmp(output_dir,"-");
  1305.  
  1306.     char *filename = NULL;
  1307.  
  1308.     /* output each page, in turn */
  1309.     for (page = first; page; page = page->next)
  1310.     {
  1311.     char *input_file_base = NULL;
  1312.     enum Output_Object output_type =
  1313.             page_output_type(page->decl_spec, page->declarator);
  1314.  
  1315.     /* the manual name is used as the output file extension, and also in
  1316.      * the nroff output header.
  1317.      */
  1318.     const char *section = page_manual_section(output_type);
  1319.  
  1320.     /* work out the base name of the file this was generated from */
  1321.     if (page->sourcefile)
  1322.     {
  1323.         const char *base = strrchr(firstpage->sourcefile, '/');
  1324.         const char *last;
  1325.     
  1326.         /* use the file name as the manual page title */
  1327.         if (base == NULL)
  1328.         base = firstpage->sourcefile;
  1329.         else
  1330.         base++;
  1331.         last = strrchr(base, '.');
  1332.         if (last == NULL)
  1333.         last = base + strlen(base);
  1334.     
  1335.         input_file_base = alloc_string(base, last);
  1336.     }
  1337.  
  1338.     if (!tostdout)
  1339.     {
  1340.         safe_free(filename);    /* free previous, if any */
  1341.         filename = page_file_name(
  1342.         use_input_name && input_file_base
  1343.                 ? input_file_base : page->declarator->name,
  1344.         output_type, section);
  1345.         fprintf(stderr,"generating: %s\n",filename);
  1346.  
  1347.         /* a previous run may have left links, so nuke old file first */
  1348.         if (!remove_old_file(filename))    exit(1);
  1349.  
  1350.         if (freopen(filename, "w", stdout) == NULL)
  1351.         {
  1352.         my_perror("error opening output file", filename);
  1353.         free(filename);
  1354.         exit(1);
  1355.         }
  1356.     }
  1357.  
  1358.     /* do the page itself */
  1359.     output_manpage(first, page, input_files,
  1360.         group_together && input_file_base ? input_file_base
  1361.                           : page->declarator->name,
  1362.         group_together ? manual_section : section);
  1363.  
  1364.     safe_free(input_file_base);
  1365.  
  1366.     /* don't continue if grouped, because all info went into this page */
  1367.     if (group_together)        break;
  1368.  
  1369.     if (tostdout &&    page->next)    output->character('\f');
  1370.     }
  1371.  
  1372.     /* close the last output file if there was one */
  1373.     if (!tostdout && fclose(stdout) == EOF)
  1374.     {
  1375.     my_perror("error linking closing file", filename);
  1376.     exit(1);
  1377.     }
  1378.  
  1379.     /* if pages are grouped, just link the rest to the first */
  1380.     if (group_together && !tostdout && link_type != LINK_NONE)
  1381.     {
  1382.     for (page=use_input_name && first->sourcefile ? first : first->next;
  1383.                             page; page = page->next)
  1384.     {
  1385.         enum Output_Object output_type =
  1386.             page_output_type(page->decl_spec, page->declarator);
  1387.         const char *extension = page_manual_section(output_type);
  1388.         char *linkname = page_file_name(page->declarator->name,
  1389.                             output_type, extension);
  1390.         int result = 0;
  1391.  
  1392.         /* we may have a function with the same name as the sourcefile */
  1393.         if (!strcmp(filename, linkname))
  1394.         {
  1395.         free(linkname);
  1396.         continue;
  1397.         }
  1398.             
  1399.         fprintf(stderr,"%s: %s\n",
  1400.         link_type == LINK_REMOVE ? "removing" : "linking", linkname);
  1401.  
  1402.         /* always nuke old output file, since it may be linked to the one
  1403.          * we've just generated, so LINK_FILE may trash it.
  1404.          */
  1405.         if (!remove_old_file(linkname))    exit(1);
  1406.  
  1407.         switch(link_type)
  1408.         {
  1409. #ifdef HAS_LINK
  1410.         case LINK_HARD:
  1411.         result = link(filename, linkname);
  1412.         break;
  1413. #endif
  1414. #ifdef HAS_SYMLINK
  1415.         case LINK_SOFT:
  1416.         result = symlink(filename, linkname);
  1417.         break;
  1418. #endif
  1419.         case LINK_FILE:
  1420.         if (freopen(linkname, "w", stdout) == NULL)
  1421.         {
  1422.             result = -1;
  1423.             break;
  1424.         }
  1425.         output_warning();
  1426.         output->include(filename);
  1427.         if (fclose(stdout) == EOF)
  1428.             result = -1;
  1429.         break;
  1430.         case LINK_NONE:
  1431.         case LINK_REMOVE:
  1432.         break;
  1433.         }
  1434.  
  1435.         /* check it went OK */
  1436.         if (result == -1)
  1437.         {
  1438.         my_perror("error linking output file", linkname);
  1439.         exit(1);
  1440.         }
  1441.         free(linkname);
  1442.     }
  1443.     }
  1444.  
  1445.     safe_free(filename);
  1446. }
  1447.